#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <mutex>
#include <boost/algorithm/string.hpp>
#include "util.hpp"

namespace ns_index
{
    // 正排索引 -- 文档id 和 内容的映射关系
    struct Doc
    {
        std::string title;
        std::string content;
        std::string url;
        uint64_t doc_id;
        bool is_ad = false;
        double bid_price = 0.0;
        double budget = 0.0;
    };
    typedef Doc Doc_t;

    // 倒排索引 -- 关键词 和 文档id 的映射关系
    // 倒排拉链 -- word 和 vector<InvertedElem> -- InvertedElem 表明当前对应哪个word，属于哪个文档，搜索权重
    struct InvertedElem
    {
        std::string word;
        uint64_t doc_id;
        int weight;
        bool is_ad = false;
        double bid_price = 0.0;
        double budget = 0.0;
    };
    typedef std::vector<InvertedElem> InvertedList;

    class Index
    {
    private:
        Index() {}
        Index(const Index &) = delete;
        Index *operator=(const Index &) = delete;

        static Index *instance;
        static std::mutex mtx;

    public:
        ~Index() {}
        static Index *GetInstance()
        {
            if (instance == nullptr)
            {
                mtx.lock();
                if (instance == nullptr)
                {
                    instance = new Index();
                }
                mtx.unlock();
            }

            return instance;
        }

        Doc_t *GetForwardIndex(uint64_t doc_id)
        {
            if (doc_id > forward_index.size())
            {
                std::cerr << doc_id << " out of range, error!" << std::endl;
                return nullptr;
            }
            return &forward_index[doc_id];
        }

        InvertedList *GetInvertedIndex(const std::string &word)
        {
            auto it = inverted_list.find(word);
            if (it == inverted_list.end())
            {
                std::cerr << word << " has no inverted list" << std::endl;
                return nullptr;
            }
            return &inverted_list[word];
        }

        // 传入构建索引的文件路径 data/raw_html/raw.txt
        bool BuildIndex(const std::string &input_path)
        {
            // for debug
            // std::cout << "begin to build index" << std::endl;

            std::ifstream in(input_path, std::ios::in | std::ios::binary);
            if (!in.is_open())
            {
                std::cerr << input_path << " open failed!" << std::endl;
                return false;
            }

            int count = 0;

            std::string line;
            while (std::getline(in, line))
            {
                // std::cout << "std::getline(in, line)" << std::endl;
                Doc_t *doc = BuildForwardIndex(line);
                if (doc == nullptr)
                {
                    std::cerr << "build forward index failed: \n"
                              << line << std::endl;
                    continue;

                    // // for debug
                    // break;
                }

                BuildInvertedIndex(*doc);
                ++count;

                if (count % 50 == 0)
                {
                    std::cout << "当前已建立索引的文章数量: " << count << "+" << std::endl;
                }
            }
            in.close();

            // std::cout << "build index end" << std::endl;
            return true;
        }

    private:
        Doc_t *BuildForwardIndex(const std::string &file)
        {
            const std::string sep = "\3";
            std::vector<std::string> doc_lines; // 用于保存分割后的 doc 内容
            ns_util::StringUtil::Split(file, &doc_lines, sep);

            // title content url is_ad bid_price budget
            if (doc_lines.size() != 6)
            {
                return nullptr;
            }

            Doc_t doc;
            doc.title = doc_lines[0];
            doc.content = doc_lines[1];
            doc.url = doc_lines[2];
            doc.doc_id = forward_index.size();

        
            doc.is_ad = (doc_lines[3] == "1");
            doc.bid_price = std::stod(doc_lines[4]);
            doc.budget = std::stod(doc_lines[5]);
        

            forward_index.push_back(std::move(doc));
            return &forward_index.back();
        }

        bool BuildInvertedIndex(const Doc_t &doc)
        {
            // 用于统计一个词在 doc 的标题和正文中的词频
            struct word_cnt
            {
                uint64_t title_cnt;
                uint64_t content_cnt;
            };

            std::unordered_map<std::string, word_cnt> word_frequency;

            // 1. 对 doc.title 进行分词
            std::vector<std::string> title_words;
            ns_util::JiebaUtil::CutString(doc.title, &title_words);

            // 2. 对 doc.title 进行词频统计
            for (auto &word : title_words)
            {
                // 关键词转小写
                boost::to_lower(word);
                word_frequency[word].title_cnt++;
            }

            // 3. 对 doc.content 进行分词
            std::vector<std::string> content_words;
            // 去标签后的 title 内容会包含在 content 中，因此 title 中的关键词会被计算两次（分别作为 title 和 content）
            ns_util::JiebaUtil::CutString(doc.content, &content_words);

            // 4. 对 doc.content 进行词频统计
            for (auto &word : content_words)
            {
                // 关键词转小写
                boost::to_lower(word);
                word_frequency[word].content_cnt++;
            }

#define X 2
// for debug : 方便确认权重是否计算正确
// #define X 10
#define Y 1
            // 5. 构建倒排拉链单元
            for (auto &pair : word_frequency)
            {
                InvertedElem item;
                item.doc_id = doc.doc_id;
                item.word = pair.first;
                item.weight = X * pair.second.title_cnt + Y * pair.second.content_cnt;

                // 增加竞价部分
                item.is_ad = doc.is_ad;
                item.bid_price = doc.bid_price;

                InvertedList &list = inverted_list[pair.first];
                list.push_back(std::move(item));
            }

            return true;
        }

    private:
        std::vector<Doc_t> forward_index;                            // 正排索引
        std::unordered_map<std::string, InvertedList> inverted_list; // 倒排索引
    };

    Index *Index::instance = nullptr;
    std::mutex Index::mtx;
}
